/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */


import java.net.Socket;
import java.net.ServerSocket;
import java.io.*;
import javax.imageio.stream.FileImageInputStream;
import javax.imageio.stream.FileImageOutputStream;
import java.io.BufferedInputStream;

/**
 *
 * @author juan
 */
class DTP implements Runnable {

    /**
     * @param mode either DTP.ACTIVE_MODE or DTP.PASSIVE_MODE
     * @param host the ip of this server o the client (depending of mode)
     * @param port that the server or client listens (depending of mode)
     * @param pi   a reference to the PI instance that is creating the DTP
     */
    public DTP(int mode, String host, int port, PI pi) {
        this.pi = pi;
        this.mode = mode;
        this.host = host;
        this.port = port;
        this.transferType = IMAGE;
    }

    public void print(Object st, Object... args) {
    
    }

    /**
     * this procedure is called to close all connections.
     */
    public void closeConnections() {
        try {
            pi.dnotice(dcs + "closing.");
            dcout.flush();
            dcout.close();
            dcin.close();
            dcs.close();
            connected = false;
        } catch (Exception e) {
            print(e);
        }
    }

    /**
     * calls queueCommand(n,null)
     * @param n
     */
    public void queueCommand(int n) {
        queueCommand(n, null);
    }

    /**
     * Excecutes a procedure in a new Thread allowing to run only 1 at the same
     * time.
     * @param n
     * @param arg
     */
    public void queueCommand(int n, String arg) {
        try {
            synchronized (lock) {
                while (busy) {
                    lock.wait();
                }
                busy = true;
                proc = n;
                this.arg = arg;
                new Thread(this).start();
            }
        } catch (Exception e) {
            pi.dnotice("Error while queueing a command: " + e);
        }
    }

    /**
     * Returns true if the data connection has been established.
     * @return true if the data connection has been established.
     */
    public boolean isConnected() {
        return dcin != null && dcout != null;
    }

    /**
     * msg is sent througth the data connection in ascii mode.
     * @param msg
     * @param args
     * @throws Exception
     */
    public void transfer(String msg, Object... args) throws Exception {
        dcout.printf(msg + "\r\n", args);
        print("dtp ASCII transfer:"+msg+"\r\n", args);
    }

    /**
     * returns a string indicating the permitions of the user over this file
     *           u  g   o     user, group,others
     *        d rwx rwx rwx
     * @param f the file.
     * @return string indicating the permitions of the user over this file
     */
    public String getFilePerm(File f) {
        String s = "";
        if (f.isDirectory()) {
            s += "d";
        } else {
            s += "-";
        }
        return s + "rw-r--r--";
    }

    /**
     * This procedure handles unhandled exceptions from commands and closes the
     * connection.
     * @param e
     */
    public void handleException(Exception e) {
        print("-----------Error Details-----------");
        e.printStackTrace();
        print("-----------Error Details End-------");
        print("-----------Error Response----------");
        if (connected == false) {
            pi.response("425 Can't open data connection.");
        } else if (e instanceof IOException) {
            pi.response("426 Connection closed; transfer aborted.");
        } else {
            pi.response("450 Requested file action not taken.");
        }
        closeConnections();
        print("-----------Error Response End-------");
    }

    public void connect() throws Exception {
        try {
            if (this.mode == PI.PASSIVE_MODE) {
                ServerSocket ss = new ServerSocket(port);
                int lp = ss.getLocalPort();
                pi.response("227 Entering Passive Mode (%s,%s,%s).",
                        host.replace(".", ","), (lp & 65280) >> 8, (lp & 255));

                dcs = ss.accept();
                ss.close();
            } else {
                pi.response("200 connecting to client");
                dcs = new Socket(host, port);
            }
            
            dcin = new BufferedReader(new InputStreamReader(dcs.getInputStream()));
            dcout = new PrintStream(dcs.getOutputStream());
            connected = true;
            connectionFailed = false;
            print("DTP Connection succed");
        } catch (Exception e) {
            print("DTP Connection failed");
            connectionFailed = true;
            handleException(e);
        }
    }

    public void list() throws Exception {
        String dir = pi.getRealWorkingDir();
        file = new File(dir);
        String fileInWD[] = file.list();

        //print("files in '%s'\n", dir);
        for (int i = 0; i < fileInWD.length; i++) {
            String d = dir + "/" + fileInWD[i];
            File f = new File(d);
            //print("FILE:#####" + d);

            transfer("%s %d %s %s %d %s %s", getFilePerm(f), 2,
                    pi.getUserName(), pi.getUserGroup(), f.length(),
                        "Dec 9 22:37", f.getName());

            
        }
        if(!pi.getRelativeWorkingDir().equals("")) {
            transfer("dr--r--r-- 2 %s %s 0 Dec 9 22:37 ..", pi.getUserName(), 
                        pi.getUserGroup());
        }
        
        transfer("dr--r--r-- 2 %s %s 0 Dec 9 22:37 .", pi.getUserName(),
                    pi.getUserGroup());

        dcout.flush();
        pi.response("226 Transfer OK Closing data connection");
        closeConnections();
    }

    void retrieveFile() throws Exception {
        try {
            if(this.transferType != IMAGE) {
                pi.response("504 RETR not implemented for ascii transfer");
                return;
            }

            File file = null;
            FileImageInputStream fis = null;

            if (connected) {
                pi.response("125 Data connection already open; transfer starting.");
            } else {
                pi.response("150 File status okay; about to open data connection.");
            }

            String path = pi.getRealPath(arg);
            print(path);
            file = new File(path);
            fis = new FileImageInputStream(file);

            byte buffer[] = new byte[BUFFER_SIZE];
            while (true) {
                int readed = fis.read(buffer, 0, buffer.length);
                if (readed == -1)///-1 = EOF
                    break;
                dcout.write(buffer, 0, readed);
            }

            pi.response("226 Transfer OK");
            dcout.flush();
            dcout.close();
            fis.close();
            dcs.close();
            connected = false;
        } catch(Exception e) {
            pi.response("451 "+e.toString());
            closeConnections();
        }
    }

    void storeFile() throws Exception {
        if(this.transferType != IMAGE) {
            pi.response("504 STOR not implemented for ascii transfer");
            return;
        }

        if (connectionFailed) {
            pi.response("425 Can't open data connection.");
        } else if (connected) {
            pi.response("125 Data connection already open; transfer starting.");
        } else {
            pi.response("150 File status okay; about to open data connection.");
        }

        try {
            String path = pi.getRealPath(arg);
            //print("STOR:   in:" + path);
            file = new File(path);
            file.delete();
            file = new File(path);
            file.createNewFile();
            InputStream is = dcs.getInputStream();
            FileImageOutputStream fos = new FileImageOutputStream(file);
            byte buffer[] = new byte[BUFFER_SIZE];
            while (true) {
                int readed = is.read(buffer);
                if (readed == -1) {
                    break;
                }
                fos.write(buffer, 0, readed);
            }
            fos.flush();
            fos.close();
            pi.response("226 Transfer OK");
        } catch(Exception e) {
            pi.response("451 "+e.toString());
            closeConnections();
        }
    }

    public void run() {
        synchronized (lock) {
            try {
                switch (proc) {
                    case CONNECT:
                        connect();
                        break;
                    case LIST:
                        list();
                        break;
                    case RETR:
                        retrieveFile();
                        break;
                    case STOR:
                        storeFile();
                        break;
                }
            } catch (Exception e) {
                handleException(e);
            }
            busy = false;
            lock.notifyAll();
        }
    }
    int proc = -1;
    Object lock = new Object();
    boolean busy = false;
    boolean connected = false, connectionFailed = false;
    /******/
    int mode, port, transferType = ASCII;
    String host, arg;
    Socket dcs, ccs; //data conn socket
    //BufferedReader dcin; //data conn input
    BufferedReader dcin;
    PrintStream dcout; //data conn output
    BufferedInputStream bis = null;
    PI pi;
    File file;
    public final static int CONNECT = 0, LIST = 1, RETR = 2, STOR = 3, BUFFER_SIZE = 65536;
    public final static int IMAGE = 0, ASCII = 1;
}
